require 'socket' # Provides TCPServer and TCPSocket classes
require 'uri'

if !ARGV[0].nil? && ARGV[0].respond_to?(:to_i)
  port_number = ARGV[0].to_i
else
  port_number = 2345
end

#Files served from this directory
WEB_ROOT = './lib/gator_serve'

#Map extensions to content type
CONTENT_TYPE_MAPPING = {
  'html' => 'text/html',
  'txt' => 'text/plain',
  'png' => 'image/png',
  'jpg' => 'image/jpeg',
  'g8r' => 'text/html'
}

#Treat as binary data if content type not defined above
DEFAULT_CONTENT_TYPE = 'application/octet-stream'

#helper method for parsing file extension of requested file
def content_type(path)
  ext = File.extname(path).split(".").last
  CONTENT_TYPE_MAPPING.fetch(ext, DEFAULT_CONTENT_TYPE)
end

#this helper function parses the Request-Line and
#generates a relative path to a file in the server's
#public folder by joining it with the WEB_ROOT
def requested_file(request_line)
  request_uri = request_line.split(" ")[1]
  path        = URI.unescape(URI(request_uri).path)

  clean = []

  #split the path into components
  parts = path.split("/")

  parts.each do |part|
    #skip . and empty path notations
    next if part.empty? || part == '.'
    #if path component is .. then remove the last clean component
    part == '..' ? clean.pop : clean << part
  end

  #return the web root joined to the clean path
  File.join(WEB_ROOT, *clean)
end

# Initialize a TCPServer object that will listen
# on localhost:2345 for incoming connections.
server = TCPServer.new('localhost', port_number)

# loop infinitely, processing one incoming
# connection at a time.
STDERR.puts "Starting GATOR-SERVE v0.0.1 on port #{port_number}."
loop do
  #my custom console message
  STDERR.puts "Listening for client's request at localhost:#{port_number}..."

  # Wait until a client connects, then return a TCPSocket
  # that can be used in a similar fashion to other Ruby
  # I/O objects. (In fact, TCPSocket is a subclass of IO.)
  socket = server.accept

  # Read the first line of the request (the Request-Line)
  request_line = socket.gets

  # Log the request to the console for debugging
  STDERR.puts request_line

  #parse for the requested file
  path = requested_file(request_line)
  path = File.join(path, 'index.html') if File.directory?(path)

  #make sure the file exists and is not a directory
  #before attempting to open it.
  # We need to include the Content-Type and Content-Length headers
  # to let the client know the size and type of data
  # contained in the response. Note that HTTP is whitespace
  # sensitive, and expects each header line to end with CRLF (i.e. "\r\n")
  if File.exist?(path) && !File.directory?(path)
    File.open(path, "rb") do |file|
      socket.print "HTTP/1.1 200 OK\r\n" +
                   "Content-Type: #{content_type(file)}\r\n" +
                   "Content-Length: #{file.size}\r\n" +
                   "Connection: close\r\n"
      # Print a blank line to separate the header from the response body,
      # as required by protocol
      socket.print "\r\n"

      #this is the response body
      #write the contents of the file to the socket
      IO.copy_stream(file, socket)
    end
  else
    message = "File not found\n"
    STDERR.print message

    #respond with a 404, DNE
    socket.print  "HTTP/1.1 404 Not Found\r\n" +
                  "Content-Type: text/plain\r\n" +
                  "Content-Length: #{message.size}\r\n" +
                  "Connection: close\r\n"

    socket.print "\r\n"

    #this sends the response body for the error page,
    #as defined above
    socket.print message
  end


  # Close the socket, terminating the connection
  socket.close
end
